/**
 * VksPay个人支付接口
 * 请勿修改此处代码，因为插件更新后此处代码会被覆盖。
 * 作者：VK
 */
const util = require("./util");
const libs = require("../../libs");
const dao = require("../../dao");

/**
 * VksPay个人支付接口，支持个人无需营业执照即可签约开户，正规通道，非市面上的挂机免签。（同时也支持企业签约）
 * 提供支付异步回调接口、下单接口、查询订单接口、退款接口、退款查询接口。
 * 消费者付款资金直接进入您签约的支付宝、微信支付商户号里，支付资金由支付宝、微信支付官方结算，避免二次清算。
 * 详情联系QQ：370725567 获取mchId和key
 */
class VksPay {
	constructor(config) {
		let {
			mchId, // 对应商户机构号
			key, // 对应商户机构key
			apiUrl = "https://vk-spay-openapi.fsq.pub/api",
		} = config;

		this.config = {
			mchId,
			key,
			apiUrl,
		};
	}
	/**
	 * 发起请求
	 */
	async request(obj = {}) {
		let { method, data = {}, successText } = obj;

		let config = this.config; // 支付配置
		let time = Date.now(); // 当前时间

		// 构造请求参数
		let params = {
			method,
			timestamp: parseInt(time / 1000), // 只需要到秒的时间戳
			signaturenonce: libs.common.random(8),
			version: "2.0",
			...data,
			merchantno: config.mchId,
		};

		// 去除值为undefined的字段
		params = JSON.parse(JSON.stringify(params));
		// 对象属性排序
		params = util.objectKeySort(params);
		// json对象转url参数字符串
		let signStr = util.jsonToUrlString(params);
		// 拼接key
		signStr += `&key=${config.key}`;
		// 进行MD5签名
		let sign = util.md5(signStr);
		// 请求参数带上签名
		params.sign = sign;
		// 发起http请求
		let res = await libs.common.request({
			url: config.apiUrl,
			method: "POST",
			contentType: "json",
			timeout: 10000,
			data: params,
		});

		// 解析返回结果
		let code =
			typeof res.result_code !== "undefined"
				? res.result_code
				: res.return_code;
		let msg =
			typeof res.result_msg !== "undefined" ? res.result_msg : res.return_msg;

		if (code === "SUCCESS") {
			// 成功
			res.code = 0;
			res.msg = successText || msg;
		} else {
			// 失败
			res.code = code;
			res.msg = msg;
		}
		res.mchId = config.mchId;

		delete res.result_code;
		delete res.result_msg;
		delete res.return_code;
		delete res.return_msg;

		return res;
	}

	/**
	 * 获取支付参数
	 * @param {Number} totalFee 支付金额 以分为单位
	 * @param {String} notifyUrl 异步回调地址
	 * @param {String} method webpay 收银台支付 micropay 刷卡支付 unifiedorder 统一下单
	 * @param {String} openid
	 * @param {String} payType 支付方式 微信 wxpay 支付宝 alipay
	 * @param {String} tradeType 交易类型
	 * 	@value JSAPI   适用于：微信公众号支付、微信小程序支付、支付宝小程序支付
	 * 	@value APP     适用于：支付宝、微信APP支付
	 * 	@value NATIVE  适用于：支付宝、微信PC网站扫码支付
	 * @param {String} spbillCreateIp 客户端ip
	 * @param {String} authCode 授权码
	 */
	async getOrderInfo(data = {}) {
		let {
			totalFee,
			notifyUrl,
			method = "webpay",
			openid,
			payType,
			tradeType,
			spbillCreateIp,
			authCode,
			returnUrl,
			outTradeNo,
		} = data;

		if (notifyUrl && notifyUrl.length > 200) {
			throw new Error(
				"notify_url异步回调地址的字符串长度不能大于200，请修改回调地址，如有疑问，可加QQ：370725567 解决"
			);
		}

		let params = {};
		if (method === "webpay") {
			// 收银台支付
			params = {
				total_fee: util.toDecimal(totalFee / 100, 2),
				notify_url: notifyUrl,
				detail_url: returnUrl,
				third_trade_no: outTradeNo,
			};
		} else if (method === "unifiedorder") {
			// 统一下单支付
			// 交易类型 微信小程序 MINIAPP 微信公众号 JSAPI 网页支付 NATIVE APP支付 APP
			if (payType === "wxpay" && tradeType === "JSAPI") {
				tradeType = "MINIAPP";
			}
			params = {
				total_fee: util.toDecimal(totalFee / 100, 2),
				notify_url: notifyUrl,
				pay_cate: "pay",
				pay_chnl: payType,
				trade_type: tradeType,
				sub_user_id: openid,
				user_id: openid,
				detail_url: returnUrl,
				third_trade_no: outTradeNo,
			};
		} else if (method === "micropay") {
			// 刷卡支付
			params = {
				total_fee: util.toDecimal(totalFee / 100, 2),
				notify_url: notifyUrl,
				pay_chnl: payType,
				client_ip: spbillCreateIp,
				auth_code: authCode,
				pay_cate: "pay",
				cashier: "default",
				kuantai: "default",
				source: "default",
				detail_url: returnUrl,
				third_trade_no: outTradeNo,
			};
		}
		let result = await this.request({
			method: "webpay",
			data: params,
		});
		if (result.code == 0) {
			// 成功
			return {
				code: 0,
				msg: "ok",
				mchId: result.mchId,
				codeUrl: result.data.qrcode,
				transaction_id: result.data.out_trade_no, // VksPay接口返回的支付单号，后续查询订单和退款都是用的Ta
				resultCode: "SUCCESS",
				returnCode: "SUCCESS",
				returnMsg: "OK",
				tradeType: tradeType,
				result,
			};
		} else {
			// 失败则直接抛出异常，对齐原本的uni-pay接口
			throw new Error(result.code);
		}
	}

	/**
	 * 查询订单
	 * @param {String} outTradeNo getOrderInfo返回的out_trade_no支付单号
	 */
	async orderQuery(data = {}) {
		let { outTradeNo, transactionId } = data;
		if (!transactionId && !outTradeNo) {
			return {
				code: -1,
				msg: "outTradeNo和transactionId不能均为空",
				tradeStateDesc: "outTradeNo和transactionId不能均为空",
				tradeState: "ERROR",
			};
		}
		if (outTradeNo) {
			let getTransactionIdRes = await this.getTransactionId({
				outTradeNo,
			});
			transactionId = getTransactionIdRes.transaction_id;
		}
		if (!transactionId) {
			// 从数据库中获取三方支付单号
			let payOrderInfo = await dao.payOrders.find({
				out_trade_no: outTradeNo,
			});
			if (!payOrderInfo) {
				return {
					code: -1,
					msg: "订单不存在",
					tradeStateDesc: "订单不存在",
					tradeState: "ERROR",
				};
			}
			if (payOrderInfo.status === 3) {
				return {
					code: -1,
					msg: "订单已退款",
					tradeStateDesc: "订单已退款",
					tradeState: "CLOSED",
				};
			}
			transactionId = payOrderInfo.transaction_id;
		}
		let result = await this.request({
			method: "orderquery",
			data: {
				out_trade_no: transactionId,
			},
		});
		if (result.code == 0) {
			// 成功
			return {
				code: 0,
				msg: "ok",
				tradeState: "SUCCESS",
				result,
			};
		} else {
			// 失败
			return {
				code: result.code,
				msg: result.msg,
				tradeStateDesc: result.msg,
				tradeState: "FAIL",
				result,
			};
		}
	}

	/**
	 * 申请退款
	 * @param {String} outTradeNo getOrderInfo返回的out_trade_no支付单号
	 * @param {String} refundFee 退款金额，单位分
	 */
	async refund(data = {}) {
		let { outTradeNo, transactionId, refundFee } = data;

		if (outTradeNo) {
			let getTransactionIdRes = await this.getTransactionId({
				outTradeNo,
			});
			transactionId = getTransactionIdRes.transaction_id;
		}

		if (!transactionId) {
			let payOrderInfo = await dao.payOrders.find({
				out_trade_no: outTradeNo,
			});
			transactionId = payOrderInfo.transaction_id;
		}

		let result = await this.request({
			method: "orderrefund",
			data: {
				out_trade_no: transactionId,
				refund_fee: util.toDecimal(refundFee / 100, 2),
			},
			successText: "请求成功",
		});

		if (result.code == 0) {
			// 成功
			return {
				code: 0,
				msg: "ok",
				outTradeNo: outTradeNo,
				transactionId: transactionId,
				refundId: result.out_refund_no,
				refundFee: refundFee,
				cashRefundFee: refundFee,
				result,
			};
		} else {
			// 失败
			return {
				code: result.code,
				msg: result.msg,
				subMsg: result.msg,
				result,
			};
		}
	}

	/**
	 * 查询退款
	 * @param {String} outTradeNo getOrderInfo返回的out_trade_no支付单号
	 * @param {String} outRefundNo refund返回的out_refund_no退款单号
	 */
	async refundQuery(data = {}) {
		let { outTradeNo, transactionId, outRefundNo, refundId } = data;

		let payOrderInfo = await dao.payOrders.find({
			out_trade_no: outTradeNo,
			transaction_id: transactionId,
		});

		let refundInfo = payOrderInfo.refund_list.find((item) => {
			if (outRefundNo) {
				return outRefundNo === item.out_refund_no;
			} else if (refundId) {
				return refundId === item.refund_id;
			}
		});

		let result = await this.request({
			method: "orderrefundquery",
			data: {
				out_trade_no: payOrderInfo.transaction_id,
				out_refund_no: refundInfo.refund_id,
			},
			successText: "请求成功",
		});

		if (result.code == 0) {
			// 成功
			return {
				code: 0,
				msg: "ok",
				outTradeNo: outTradeNo,
				transactionId: payOrderInfo.transaction_id,
				totalFee: payOrderInfo.total_fee,
				refundId: refundInfo.refund_id,
				refundFee: refundInfo.refund_fee,
				refundDesc: refundInfo.refund_desc,
				result,
			};
		} else {
			// 失败
			return {
				code: result.code,
				msg: result.msg,
				subMsg: result.msg,
				result,
			};
		}
	}

	/**
	 * 关闭订单
	 * @param {String} outTradeNo getOrderInfo返回的out_trade_no支付单号
	 */
	async closeOrder(data = {}) {
		let { outTradeNo, transactionId } = data;

		if (!transactionId) {
			let payOrderInfo = await dao.payOrders.find({
				out_trade_no: outTradeNo,
			});
			transactionId = payOrderInfo.transaction_id;
		}

		let result = await this.request({
			method: "orderclose",
			data: {
				out_trade_no: transactionId,
			},
			successText: "请求成功",
		});

		if (result.code == 0) {
			// 成功
			return {
				code: 0,
				msg: "ok",
				outTradeNo: outTradeNo,
				transactionId: transactionId,
				result,
			};
		} else {
			// 失败
			return {
				code: result.code,
				msg: result.msg,
				subMsg: result.msg,
				result,
			};
		}

		return res;
	}

	/**
	 * 撤销订单（依然走关闭订单逻辑）
	 */
	async cancelOrder(data = {}) {
		return await this.closeOrder(data);
	}

	/**
	 * 支付结果通知处理
	 */
	async verifyPaymentNotify(httpInfo = {}) {
		let body = httpInfo.body;
		if (httpInfo.isBase64Encoded) {
			body = Buffer.from(body, "base64").toString("utf-8");
		}
		if (typeof body === "string") {
			body = util.urlStringToJson(body);
		}

		let {
			out_trade_no: transaction_id, // 支付平台单号
			openid,
			total_fee,
			cope_fee,
			merchantno,
			third_trade_no: out_trade_no, // 商户订单号
			pay_chnl,
			trade_type,
			tradeno,
			detail_url,
			goods_detail,
			kuantai,
			cashier,
			attach,
		} = body;

		let orderQueryRes;
		// 如果报异常，最多试3次
		let retryCount = 3;
		for (let i = 0; i < retryCount; i++) {
			try {
				orderQueryRes = await this.orderQuery({
					transactionId: transaction_id,
				});
				if (orderQueryRes && typeof orderQueryRes.code === "number") {
					break;
				}
			} catch (err) {
				if (i === retryCount - 1) {
					throw err;
				}
			}
		}

		if (orderQueryRes.code !== 0) {
			// 若订单未支付，则直接返回null
			return null;
		}
		// 若订单号和金额不一致，同样返回null（由于vkspay的回调没有签名，因此此处判断是为了防止伪造）
		if (out_trade_no && orderQueryRes.result.third_trade_no !== out_trade_no) {
			return null;
		}
		if (Number(orderQueryRes.result.total_fee) !== Number(total_fee)) {
			return null;
		}

		let timeEnd = libs.common.timeFormat(new Date(), "yyyyMMddHHmmss");
		let returnCode = orderQueryRes.code === 0 ? "SUCCESS" : "FAIL";
		let res = {
			mchId: merchantno,
			totalFee: util.toDecimal(total_fee * 100, 0),
			cashFee: util.toDecimal(cope_fee * 100, 0),
			outTradeNo: out_trade_no,
			transactionId: transaction_id,
			timeEnd: timeEnd,
			openid: openid,
			payMethod: pay_chnl,
			tradeType: trade_type,
			tradeno,
			returnUrl: detail_url,
			goodsDetail: goods_detail,
			kuantai,
			cashier,
			attach,
			returnCode: returnCode,
			resultCode: returnCode,
		};
		return res;
	}

	// /**
	//  * 退款结果通知（此接口不支持）
	//  */
	// async verifyRefundNotify(data = {}) {
	// 	throw new Error("VksPay不支持verifyRefundNotify")
	// }

	/**
	 * 获取通知类型（此接口不支持）
	 */
	async checkNotifyType(data = {}) {
		return "payment";
	}

	/**
	 * 获取并修正订单out_trade_no与transaction_id的关系
	 */
	async getTransactionId(data = {}) {
		let { outTradeNo } = data;

		let res = { code: 0, msg: "" };

		if (!outTradeNo) {
			return { code: -1, msg: "outTradeNo不能为空" };
		}

		// 从数据库中获取订单信息
		let payOrderInfo = await dao.payOrders.find({
			out_trade_no: outTradeNo,
		});
		if (!payOrderInfo) {
			return { code: -1, msg: "订单不存在" };
		}

		if (payOrderInfo.notify_date) {
			res.out_trade_no = outTradeNo;
			res.transaction_id = payOrderInfo.transaction_id;
		} else if (
			payOrderInfo.transaction_ids &&
			payOrderInfo.transaction_ids.length > 0
		) {
			if (payOrderInfo.transaction_ids.length === 1) {
				res.out_trade_no = outTradeNo;
				res.transaction_id = payOrderInfo.transaction_id;
			} else {
				for (let i = 0; i < payOrderInfo.transaction_ids.length; i++) {
					let transaction_id = payOrderInfo.transaction_ids[i];
					let orderQueryRes = await this.request({
						method: "orderquery",
						data: {
							out_trade_no: transaction_id,
						},
					});
					if (orderQueryRes.code === 0) {
						res.out_trade_no = outTradeNo;
						res.transaction_id = transaction_id;
						break;
					}
				}
			}
		}
		if (
			res.out_trade_no &&
			res.transaction_id &&
			res.transaction_id !== payOrderInfo.transaction_id
		) {
			await dao.payOrders.updateAndReturn({
				whereJson: {
					out_trade_no: res.out_trade_no,
				},
				dataJson: {
					transaction_id: res.transaction_id,
				},
			});
		}
		return res;
	}
}

module.exports = VksPay;
